package templates

import (
	"fmt"

	"libvirt.org/go/libvirtxml"
)

// BootMode represents boot firmware type
type BootMode string

const (
	BootModeBIOS BootMode = "bios"
	BootModeUEFI BootMode = "uefi"
)

// Architecture represents CPU architecture
type Architecture string

const (
	ArchX86_64  Architecture = "x86_64"
	ArchAARCH64 Architecture = "aarch64"
)

// DomainTemplate holds configuration for creating a VM
type DomainTemplate struct {
	Name         string
	UUID         string
	Memory       uint // Memory in MiB
	VCPUs        uint
	Architecture Architecture
	BootMode     BootMode
	DiskPath     string
	DiskSize     uint64 // Disk size in GiB
	DiskBus      string // virtio, sata, scsi
	NetworkMode  string // network, bridge, user
	NetworkName  string // Network name for network mode
	OSVariant    string // OS variant ID for libosinfo
	EnableUEFI   bool
	SecureBoot   bool

	// CDROM configuration
	CDROMPath string // Path to CDROM ISO image
	CDROMBus  string // Bus for CDROM device (ide, sata, scsi)
	CDROMBoot bool   // Boot from CDROM
}

// Builder creates domain XML from templates
type Builder struct{}

// NewBuilder creates a new template builder
func NewBuilder() *Builder {
	return &Builder{}
}

// Build generates domain XML from template
func (b *Builder) Build(tmpl *DomainTemplate) (string, error) {
	if err := b.validate(tmpl); err != nil {
		return "", err
	}

	domain := &libvirtxml.Domain{
		Type: "kvm",
		Name: tmpl.Name,
		UUID: tmpl.UUID,
		Memory: &libvirtxml.DomainMemory{
			Value: tmpl.Memory * 1024, // Convert MiB to KiB
			Unit:  "KiB",
		},
		CurrentMemory: &libvirtxml.DomainCurrentMemory{
			Value: tmpl.Memory * 1024,
			Unit:  "KiB",
		},
		VCPU: &libvirtxml.DomainVCPU{
			Placement: "static",
			Value:     tmpl.VCPUs,
		},
	}

	// Add libosinfo metadata if OS variant specified
	if tmpl.OSVariant != "" {
		domain.Metadata = &libvirtxml.DomainMetadata{
			XML: fmt.Sprintf(`<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
      <libosinfo:os id="%s"/>
    </libosinfo:libosinfo>`, tmpl.OSVariant),
		}
	}

	// Configure OS settings based on architecture and boot mode
	b.configureOS(domain, tmpl)

	// Add features
	domain.Features = b.buildFeatures(tmpl)

	// Configure CPU
	domain.CPU = b.configureCPU(tmpl)

	// Add clock configuration
	domain.Clock = b.buildClock()

	// Add lifecycle actions
	domain.OnPoweroff = "destroy"
	domain.OnReboot = "restart"
	domain.OnCrash = "destroy"

	// Add power management
	domain.PM = &libvirtxml.DomainPM{
		SuspendToMem:  &libvirtxml.DomainPMPolicy{Enabled: "no"},
		SuspendToDisk: &libvirtxml.DomainPMPolicy{Enabled: "no"},
	}

	// Add devices
	domain.Devices = b.buildDevices(tmpl)

	// Marshal to XML
	xmlBytes, err := domain.Marshal()
	if err != nil {
		return "", fmt.Errorf("failed to marshal domain XML: %w", err)
	}

	return string(xmlBytes), nil
}

func (b *Builder) validate(tmpl *DomainTemplate) error {
	if tmpl.Name == "" {
		return fmt.Errorf("domain name is required")
	}
	if tmpl.Memory == 0 {
		return fmt.Errorf("memory must be greater than 0")
	}
	if tmpl.VCPUs == 0 {
		return fmt.Errorf("vcpus must be greater than 0")
	}
	switch tmpl.Architecture {
	case ArchX86_64, ArchAARCH64:
		// Valid architectures
	default:
		return fmt.Errorf("unsupported architecture: %s", tmpl.Architecture)
	}
	if tmpl.DiskPath == "" {
		return fmt.Errorf("disk path is required")
	}
	return nil
}

func (b *Builder) configureOS(domain *libvirtxml.Domain, tmpl *DomainTemplate) {
	osType := &libvirtxml.DomainOSType{
		Arch: string(tmpl.Architecture),
		Type: "hvm",
	}

	// Set machine type based on architecture
	switch tmpl.Architecture {
	case ArchX86_64:
		osType.Machine = "pc-q35-8.2"
	case ArchAARCH64:
		osType.Machine = "virt"
	}

	// Configure boot devices
	bootDevices := []libvirtxml.DomainBootDevice{
		{Dev: "hd"},
	}

	// Add CDROM as boot device if requested
	if tmpl.CDROMBoot {
		bootDevices = append([]libvirtxml.DomainBootDevice{{Dev: "cdrom"}}, bootDevices...)
	}

	os := &libvirtxml.DomainOS{
		Type:        osType,
		BootDevices: bootDevices,
	}

	// Configure UEFI if requested
	if tmpl.BootMode == BootModeUEFI {
		switch tmpl.Architecture {
		case ArchX86_64:
			os.Loader = &libvirtxml.DomainLoader{
				Readonly: "yes",
				Type:     "pflash",
				Path:     "/usr/share/OVMF/OVMF_CODE.fd",
			}
			os.NVRam = &libvirtxml.DomainNVRam{
				Template: "/usr/share/OVMF/OVMF_VARS.fd",
			}
		case ArchAARCH64:
			os.Loader = &libvirtxml.DomainLoader{
				Readonly: "yes",
				Type:     "pflash",
				Path:     "/usr/share/AAVMF/AAVMF_CODE.fd",
			}
			os.NVRam = &libvirtxml.DomainNVRam{
				Template: "/usr/share/AAVMF/AAVMF_VARS.fd",
			}
		}

		// Enable secure boot if requested
		if tmpl.SecureBoot {
			os.Loader.Secure = "yes"
		}
	}

	domain.OS = os
}

func (b *Builder) buildFeatures(tmpl *DomainTemplate) *libvirtxml.DomainFeatureList {
	features := &libvirtxml.DomainFeatureList{
		ACPI: &libvirtxml.DomainFeature{},
		APIC: &libvirtxml.DomainFeatureAPIC{},
	}

	// VMPort should be disabled
	features.VMPort = &libvirtxml.DomainFeatureState{State: "off"}

	// Add GIC for ARM64
	if tmpl.Architecture == ArchAARCH64 {
		features.GIC = &libvirtxml.DomainFeatureGIC{
			Version: "3",
		}
	}

	// Add SMM for UEFI Secure Boot
	if tmpl.BootMode == BootModeUEFI && tmpl.SecureBoot {
		features.SMM = &libvirtxml.DomainFeatureSMM{State: "on"}
	}

	return features
}

func (b *Builder) configureCPU(tmpl *DomainTemplate) *libvirtxml.DomainCPU {
	cpu := &libvirtxml.DomainCPU{
		Mode:       "host-passthrough",
		Check:      "none",
		Migratable: "on",
	}

	// Configure CPU topology based on vCPU count for better performance
	if tmpl.VCPUs > 0 {
		// Set reasonable defaults for CPU topology
		// Use 1 socket, 1 core per vCPU, 1 thread per core for simplicity
		cpu.Topology = &libvirtxml.DomainCPUTopology{
			Sockets: 1,
			Cores:   int(tmpl.VCPUs),
			Threads: 1,
		}
	}

	return cpu
}

func (b *Builder) buildClock() *libvirtxml.DomainClock {
	return &libvirtxml.DomainClock{
		Offset: "utc",
		Timer: []libvirtxml.DomainTimer{
			{Name: "rtc", TickPolicy: "catchup"},
			{Name: "pit", TickPolicy: "delay"},
			{Name: "hpet", Present: "no"},
		},
	}
}

func (b *Builder) buildDevices(tmpl *DomainTemplate) *libvirtxml.DomainDeviceList {
	devices := &libvirtxml.DomainDeviceList{}

	// Set emulator path
	switch tmpl.Architecture {
	case ArchX86_64:
		devices.Emulator = "/usr/bin/qemu-system-x86_64"
	case ArchAARCH64:
		devices.Emulator = "/usr/bin/qemu-system-aarch64"
	}

	// Add disks
	disks := []libvirtxml.DomainDisk{
		b.buildDisk(tmpl),
	}

	// Add CDROM if specified
	if tmpl.CDROMPath != "" {
		cdromDisk := b.buildCDROM(tmpl)
		disks = append(disks, cdromDisk)
	}

	devices.Disks = disks

	// Add controllers
	devices.Controllers = b.buildControllers(tmpl)

	// Add network interface
	devices.Interfaces = []libvirtxml.DomainInterface{
		b.buildNetworkInterface(tmpl),
	}

	// Add serial, console
	devices.Serials = []libvirtxml.DomainSerial{
		{
			Target: &libvirtxml.DomainSerialTarget{
				Port: func(p uint) *uint { return &p }(0),
			},
		},
	}

	devices.Consoles = []libvirtxml.DomainConsole{
		{
			Target: &libvirtxml.DomainConsoleTarget{
				Type: "serial",
				Port: func(p uint) *uint { return &p }(0),
			},
		},
	}

	// Add channels
	devices.Channels = b.buildChannels(tmpl)

	// Add input devices
	devices.Inputs = b.buildInputs(tmpl)

	// Add graphics (VNC and SPICE)
	devices.Graphics = b.buildGraphics(tmpl)

	// Add video device
	devices.Videos = []libvirtxml.DomainVideo{
		{
			Model: libvirtxml.DomainVideoModel{
				Type: "virtio",
			},
		},
	}

	// Add memory balloon
	devices.MemBalloon = &libvirtxml.DomainMemBalloon{
		Model: "virtio",
	}

	// Add RNG device
	devices.RNGs = []libvirtxml.DomainRNG{
		{
			Model: "virtio",
			Backend: &libvirtxml.DomainRNGBackend{
				Random: &libvirtxml.DomainRNGBackendRandom{
					Device: "/dev/urandom",
				},
			},
		},
	}

	return devices
}

func (b *Builder) buildDisk(tmpl *DomainTemplate) libvirtxml.DomainDisk {
	diskBus := tmpl.DiskBus
	if diskBus == "" {
		diskBus = "virtio"
	}

	targetDev := "vda"
	switch diskBus {
	case "sata", "scsi":
		targetDev = "sda"
	}

	return libvirtxml.DomainDisk{
		Device: "disk",
		Driver: &libvirtxml.DomainDiskDriver{
			Name: "qemu",
			Type: "qcow2",
		},
		Source: &libvirtxml.DomainDiskSource{
			File: &libvirtxml.DomainDiskSourceFile{
				File: tmpl.DiskPath,
			},
		},
		Target: &libvirtxml.DomainDiskTarget{
			Dev: targetDev,
			Bus: diskBus,
		},
	}
}

// buildCDROM creates a CDROM device configuration
func (b *Builder) buildCDROM(tmpl *DomainTemplate) libvirtxml.DomainDisk {
	cdromBus := tmpl.CDROMBus
	if cdromBus == "" {
		// Set default bus based on architecture
		switch tmpl.Architecture {
		case ArchX86_64:
			cdromBus = "ide"
		default:
			cdromBus = "scsi"
		}
	}

	// Determine target device based on bus
	targetDev := "hda"
	switch cdromBus {
	case "sata", "scsi":
		targetDev = "sdb"
	case "virtio":
		targetDev = "vdb"
	}

	return libvirtxml.DomainDisk{
		Device: "cdrom",
		Driver: &libvirtxml.DomainDiskDriver{
			Name: "qemu",
			Type: "raw",
		},
		Source: &libvirtxml.DomainDiskSource{
			File: &libvirtxml.DomainDiskSourceFile{
				File: tmpl.CDROMPath,
			},
		},
		Target: &libvirtxml.DomainDiskTarget{
			Dev: targetDev,
			Bus: cdromBus,
		},
		ReadOnly: &libvirtxml.DomainDiskReadOnly{},
	}
}

// Continued in next artifact...
